#include <cstring>
#include <iostream>
#include <fstream>
#include <cassert>

#include "ExceptionSafeThread.hpp"
#include "TraceQueue.hpp"

#include "Trace.hpp"

// To easen the code repetition.
#define CASE_MESSAGE_TYPE(msgType) case msgType : messageTypeString = #msgType" : " ; break;

namespace se{

/********************************************//**
 * The default name of the log file.
 *
 * When the program is loaded, use ConfigureLogFile
 * to change it. This is used when nothing is set.
 ***********************************************/
const char* System::Trace::s_LogDefaultFilename = "log.txt";

/********************************************//**
 * Keeps track of the existance of a trace.
 *
 ***********************************************/
System::ThreadSignal_t System::Trace::s_instanceCreated = false;

/********************************************//**
 * Ensures that the trace is deleted.
 * 
 * Some outputs may require closing in order to write
 * correctly their data, for example, buffered files.
 * 
 * This function is called when the program exits
 * to delete the trace when the program exits.
 * This is necessary to call it, because given the
 * fact se::System::trace is allocated by new,
 * the destructor won't be called.
 ***********************************************/
static void DeleteTraceAtExit(){
	delete se::System::trace;
}

/////////////////////////////////////////////////
/// \brief Create a Trace object.
///
/// Please note that you cannot use the trace without
/// calling Ready().
/// 
/// This constructor defaults the log file to "log.txt",
/// erase the previous contents and set both the console and
/// the log file to be essential.
/// All the chanels flags are set for all message type.
/// \see void System::Trace::Ready()
/////////////////////////////////////////////////
System::Trace::Trace() :
		m_started(false),
		m_isCriticalConsole(true),
		m_isCriticalLogFile(true),
		m_nameLogFile(s_LogDefaultFilename),
		m_logFile(),
		m_flushOnFirstUseLogFile(true),
		m_traceQueue(NULL),
		m_fileWritingThread(),
		m_threadRequestFinish(false){
	assert(!s_instanceCreated);
	s_instanceCreated = true;
	std::cout << "Trace"<< std::endl;
	m_fileWritingThread.SetFunctor(
			boost::bind<void>(se::System::Trace::RunThread,this));
	// Avoiding an universe collapse.
	m_fileWritingThread.SetLogging(false);
	for(int i = 0 ; i<NONE ; ++i){
		m_flag_messageType_Array[i] = OS_CONSOLE|LOG_FILE;// setting the flags
	}
}

/********************************************//**
 * Delete a \c Trace instance.
 *
 * Makes sure all the output streams are closed.
 *
 * \warning Creating a new instance while the other have
 * been deleted is unknown behaviour. But it
 * should work.
 ***********************************************/
System::Trace::~Trace(){
	if(m_started){
		m_threadRequestFinish = true;
		m_fileWritingThread.Join();
		if(m_logFile.is_open()) m_logFile.close();
		delete m_traceQueue;
	}
	if(m_logFile.is_open()) m_logFile.close();
	s_instanceCreated = false;
}

/********************************************//**
 * \brief Launch the logging thread
 * 
 * Before calling this function, the trace is useless.
 * \pre \c Ready() wan't already called. 
 * \return \a true if launched sucessfully. It always launch successfully.
 ***********************************************/
bool System::Trace::Ready(){
	assert(!m_started);
	std::ios_base::openmode flags = std::ios_base::binary|std::ios_base::out;
	if(m_flushOnFirstUseLogFile) flags |= std::ios_base::trunc;
	else                         flags |= std::ios_base::app;
	m_logFile.open(m_nameLogFile.c_str(),flags);
	if(m_isCriticalLogFile && m_logFile.fail()) LogOutputFailure();
	m_traceQueue = new TraceQueue();
	m_fileWritingThread.Start();
	atexit(DeleteTraceAtExit);
	m_started = true;
	return true;// TODO : a meaningfull return.
}
/********************************************//**
 * Creates a debug message.
 *
 * This message will be blocking. But this way it preserves causality.
 * \pre \c Ready() called.
 * \param[in] message The message to write.
 ***********************************************/
void System::Trace::Write(const std::string& message){
	assert(m_started);
	std::string tmpString("DEBUG : ");
	tmpString += message;
	m_traceQueue->Push(tmpString.c_str(), m_flag_messageType_Array[DEBUG],true);
}

/********************************************//**
 * Write a message to the trace.
 *
 * \pre Ready() called.
 * \param[in] messageType The message type.
 * \param[in] message The message content. Please note
 * that the \a \\n is added, so it is not advised to place
 * one in the string.
 * \param[in] critical Whether the message must be written
 * before the thread resumes its execution.
 ***********************************************/
void System::Trace::Write(MessageType_e messageType , const char* message, bool critical, Color_t col){
	assert(m_started);
	char buffer[255];// Sanitizing input (for a non-null terminated string)
	strncpy(buffer, message,254);
	buffer[254] = '\0';
	const char *messageTypeString = NULL;
	switch(messageType){
		CASE_MESSAGE_TYPE(DEBUG)
		CASE_MESSAGE_TYPE(EXCEPTION_HANDLED)
		CASE_MESSAGE_TYPE(EXCEPTION_UNHANDLED)
		CASE_MESSAGE_TYPE(LOADED)
		CASE_MESSAGE_TYPE(LOAD_FAILED)
		CASE_MESSAGE_TYPE(SCRIPT_OUTPUT)
		CASE_MESSAGE_TYPE(SCRIPT_ERROR)
		CASE_MESSAGE_TYPE(INFO)
		case NONE: messageTypeString = "";
	}
	std::string tmpString(messageTypeString);
	tmpString += buffer;
	m_traceQueue->Push(tmpString.c_str(),m_flag_messageType_Array[messageType],col,critical);
}

/********************************************//**
 * Called on output failure.
 *
 * If an output of a medium flagged as essential
 * fails, then this function is called.
 *
 * It intends to make easier to change this behaviour.
 *
 * The current behaviour is : exiting the logging
 * thread.
 * 
 * For now, this behaviour is very unsatisfactory,
 * forbiding (or a least gravely hindering) a save,
 * for example.
 * \todo change the behaviour. maybe using a global
 * variable "Apocalypse_ExitRequested".
 ***********************************************/
void System::Trace::LogOutputFailure(){
	exit(-3);
}

/********************************************//**
 * Gets the message text size, and cap it if needed.
 * 
 * \pre \c message is pointing on a TraceOutput_t.
 * \param[in,out] message The message to alter.
 * \param[out] size The message text length.
 ***********************************************/
void System::Trace::capMessage(TraceOutput_t* message, uint8_t& size){
	size = 0;
	while(message->text[size]!='\0' && size < sizeof(message->text)-1)
		size++;
	// Capping it (just in case).
	message->text[size] = '\0';
}

/********************************************//**
 * The function executed in the logging thread.
 * 
 * The ideal way to end it is to set m_threadRequestFinish
 * to true. Else it will keep on running indefinitely.
 ***********************************************/
void System::Trace::RunThread(void* _trace){
	Trace& trace = *((Trace*)_trace);
	TraceOutput_t *toHandle;
	uint8_t messageSize;
	//the main loop
	// TODO: see if it can't be reorganized in a cleaner way.
	do{// Counting on GetFront to tell us when this is over.
		toHandle = trace.m_traceQueue->GetFront(trace.m_threadRequestFinish);{
			if(toHandle==NULL) break;// The GetFront ended on request, and had nothing more in it.
			capMessage(toHandle,messageSize);
			// Printing it.
			if(OS_CONSOLE & toHandle->channels){
				// TODO : the color ;-P
				std::cout << toHandle->text << std::endl;
				if(toHandle->critical) std::cout.flush();
			}
			if(trace.m_logFile.is_open() &&(LOG_FILE & toHandle->channels)){
				// UGLY : The easy way for now, but this isn't realy "clean".
				trace.m_logFile.write(toHandle->text, messageSize);
				trace.m_logFile.put('\n');
				if(toHandle->critical) trace.m_logFile.flush();
			}
		}trace.m_traceQueue->PopFront();
	}while(toHandle);// Useless, just to tell vc to shut the f*** up.
}
}// </namespace se>
